import { app } from "../../base/typings/global";

enum InternalProperty {
    /**设备系统版本 */
    DEVICE_SYSTEM = "_ds_",
    /**设备操作系统类型 */
    DEVICE_PLATFORM = "_dp_",
    /**设备手机品牌 */
    DEVICE_BRAND = "_db_",
    /**设备手机型号 */
    DEVICE_MODEL = "_dm_",
    /**宿主名称 */
    HOST_NAME = "_hn_",
    /**宿主版本 */
    HOST_VERSION = "_hv_",
    /**启动时间 */
    LAUNCH_TIME = "_lt_",
    /**安装时间 */
    INSTALL_TIME = "_it_",
    /**app运载（安装）时长 */
    DURATION_OF_INSTALL = "_doi_",
    /**app运行(启动）时长 */
    DURATION_OF_LAUNCH = "_dol_",
    /**累积登录天数 */
    LOGIN_DAYS = "_lds_",
    /**安装来源type */
    INSTALL_SCENE = "_is_",
    /**安装来源source */
    INSTALL_CHANNEL = "_ic_",
    /**启动来源场景 */
    LAUNCH_SCENE = "_ls_",
    /**启动来源渠道 */
    LAUNCH_CHANNEL = "_lc_",
    /**事件计时时长 */
    DURATION = "_dt_",
    /**游戏版本 */
    APP_VERSION = "_av_",
}

/**
 * 上报数据通用格式定义,方便项目间的通用数据统计
 */
export type AnalyticsData = {
    /**
     * number类型，定义偏向于标记，可用于关卡id、道具\装备id、广告id等的标记
     */
    id?:number,
    /**
     * string类型，定义偏向于标记，可用于关卡名称、道具名称、行为分类名称等的标记
     */
    name?:string,
    /**
     * string类型，定义偏向于结果，可用于行为后的结果消息等。
     */
    msg?:string,
    /**
     * number类型，定义偏向于结果，可用于行为后的结果code等。
     */
    code?:number,
    [key:string]:string|boolean|number
}

export interface IAnalyticsSender{
    /**
     * 是否需要发送
     * @param eventName 
     */
    sendAble(eventName:string):boolean;

    /**
     * 上报
     * @param eventName 事件名称
     * @param data 事件数据
     */
    send(eventName: string, data: any): Promise<void>;
}

export class EmptyAnalyticsSender implements IAnalyticsSender{
    protected _reportError:boolean;
    protected _exclude:string[];

    /**
     * @param [reportErrr] 是否上报错误, 默认不上报
     * @param [exclude] 其他排除的上报日志, 默认不排除
     */
    constructor(reportError:boolean=false, exclude?:string[]) {
        this._reportError = reportError;
        this._exclude = exclude;
    }

    sendAble(eventName:string):boolean {
        if (!this._reportError && (eventName == "error" || eventName == "app_error")) {
            return false;
        }
        if(this._exclude && this._exclude.indexOf(eventName) != -1) {
            return false;
        }
        return true;
    }

    async send(eventName: string, data: any): Promise<void> {
        
    }
}

/**
 * 统计分析能力
 */
export class AnalyticsAble{
    private _hasSetDefaultProperty:boolean = false;
    protected _debugMode:boolean = false;
    protected _senderList:IAnalyticsSender[] = [];
    protected _staticProperties:AnalyticsData;
    protected _dynamicCalls:(()=>AnalyticsData)[] = [];
    protected _userProperties:AnalyticsData;
    protected _userOnceProperties:AnalyticsData;
    protected _userAddProperties:AnalyticsData;
    protected _timeEvents:Map<string, number>;
    /**
     * 初始化
     */
    init(senders:IAnalyticsSender[], debugMode:boolean=false): AnalyticsAble {
        this._debugMode = debugMode;
        app.App.onShow(this._onAppShow, this);
        app.App.onHide(this._onAppHide, this);
        this._initSenderList(senders);
        return this;
    }

    private _initSenderList(senders:IAnalyticsSender[]):void {
        for (let i = 0; i < senders.length; i++) {
            const sender = senders[i];
            this._senderList.push(sender);
        }
    }

    protected _onAppShow():void {
        this.time("app_hide");
        this.report("app_show", app.publisher.publisher.enterOptions.getAllOption());
    }

    protected _onAppHide():void {
        this.report("app_hide");
    }

    /**
     * 设置所有上报事件的基础属性
     */
    private _setDefaultProperty():void {
        if(this._hasSetDefaultProperty) {
            return;
        }
        //设备信息
        let deviceInfo = app.publisher.device.deviceInfo;
        this.setProperty(InternalProperty.DEVICE_SYSTEM, deviceInfo.system);
        this.setProperty(InternalProperty.DEVICE_PLATFORM, deviceInfo.platform);
        this.setProperty(InternalProperty.DEVICE_BRAND, deviceInfo.brand);
        this.setProperty(InternalProperty.DEVICE_MODEL, deviceInfo.model);
        //宿主信息
        let hostInfo = app.publisher.device.hostInfo;
        this.setProperty(InternalProperty.HOST_NAME, hostInfo.name);
        this.setProperty(InternalProperty.HOST_VERSION, hostInfo.version);
        //安装时间
        this.setProperty(InternalProperty.INSTALL_TIME, Math.floor(app.App.installTime / 1000));
        //启动时间
        this.setProperty(InternalProperty.LAUNCH_TIME, Math.floor(app.App.launchTime / 1000));
        //游戏版本
        this.setProperty(InternalProperty.APP_VERSION, app.publisher.publisher.appVersion);

        this.setDynamicProperties(()=>{
            let dynamicData:AnalyticsData = {};
            //app运载（安装）时长
            dynamicData[InternalProperty.DURATION_OF_INSTALL] = Math.floor(app.App.durationOfInstall / 1000);
            //app运行(启动）时长
            dynamicData[InternalProperty.DURATION_OF_LAUNCH] = Math.floor(app.App.durationOfLaunch / 1000);
            return dynamicData;
        });

        //累积登录天数
        this.userSet(InternalProperty.LOGIN_DAYS, app.storage.loginDays);
        //安装来源type
        this.userSet(InternalProperty.INSTALL_SCENE, app.publisher.publisher.installOptions.channelScene);
        //安装来源source
        this.userSet(InternalProperty.INSTALL_CHANNEL, app.publisher.publisher.installOptions.channelSource);
        //启动来源场景
        this.userSet(InternalProperty.LAUNCH_SCENE, app.publisher.publisher.enterOptions.channelScene);
        //启动来源渠道
        this.userSet(InternalProperty.LAUNCH_CHANNEL, app.publisher.publisher.enterOptions.channelSource);

        this._hasSetDefaultProperty = true;
    }

    /**
     * 设置事件公共属性
     * @param key 公共属性key
     * @param value 
     */
    setProperty(key:string, value:string|number|boolean):void {
        if(!this._staticProperties) {
            this._staticProperties = {};
        }
        if(typeof value === "boolean"){
            value = value ? 1 : 0;
        }
        if(this._debugMode && Object.prototype.hasOwnProperty.call(this._staticProperties, key)){
            console.warn(`静态公共属性中已经存在${key}:${this._staticProperties[key]}, 将替换为${value}`);
        }
        this._staticProperties[key] = value;
    }

    /**
     * 删除事件的公共属性
     * @param key 
     * @returns 
     */
    unsetProperty(key:string):boolean {
        if(this._staticProperties){
            return delete this._staticProperties[key];
        }
        return false;
    }

    /**
     * 设置事件公共动态属性,上报事件时会触发回调函数，并把返回的AnalyticsData加入到事件属性中
     * @param dynamicCall 动态属性的生成函数，事件上报时实时调用
     */
    setDynamicProperties(dynamicCall:()=>AnalyticsData):void {
        this._dynamicCalls.push(dynamicCall);
    }

    /**
     * 对于一般的用户属性，您可以调用 userSet 来进行设置。
     * 使用该接口上传的属性将会覆盖原有的属性值，如果之前不存在该用户属性，则会新建该用户属性，类型与传入属性的类型一致。
     * 用户名为例：
     * ```
     * // username为TA
     * userSet("username", "TA");
     * //username为TE
     * userSet("username", "TE");
     * ```
     * @param key 属性key
     * @param value 
     */
    userSet(key:string, value:string|number|boolean):void {
        if(!this._userProperties){
            this._userProperties = {};
        }
        if(typeof value === "boolean"){
            value = value ? 1 : 0;
        }
        if(this._debugMode && Object.prototype.hasOwnProperty.call(this._userProperties, key)){
            console.warn(`用户属性中已经存在${key}:${this._userProperties[key]}, 将替换为${value}`);
        }
        this._userProperties[key] = value;
    }

    /**
     * 如果您要上传的用户属性只要设置一次，则可以调用 userSetOnce 来进行设置。
     * 当该属性之前已经有值的时候，将会忽略这条信息，比如设置首次付费时间：
     * ```
     * //first_payment_time为2018-01-01 01:23:45.678
     * userOnce("first_payment_time", "2018-01-01 01:23:45.678");
     * //first_payment_time仍然为2018-01-01 01:23:45.678
     * userOnce("first_payment_time", "2018-12-31 01:23:45.678");
     *   ```
     * @param key 属性key
     * @param value 
     * @returns 
     */
    userOnce(key:string, value:string|number|boolean):void {
        if(!this._userOnceProperties){
            this._userOnceProperties = {};
        }
        if(typeof value === "boolean"){
            value = value ? 1 : 0;
        }
        if(Object.prototype.hasOwnProperty.call(this._userOnceProperties, key)){
            this._debugMode && console.info(`用户属性中已经存在once属性${key}:${this._userOnceProperties[key]}`);
            return;
        }
        let storage = app.storage.getUValue(`analytics_user_once_${key}`, "");
        if(storage != null && storage != ""){
            this._debugMode && console.info(`用户属性中已经存在once属性${key}:${storage}`);
            this._userOnceProperties[key] = typeof value === "number" ? Number(storage) : storage;
            return;
        }
        app.storage.setUValue(`analytics_user_once_${key}`, value);
        this._userOnceProperties[key] = value;
    }

    /**
     *设置用户累积属性，当您要上传数值型的属性时，您可以调用 userAdd 来对该属性进行累加操作，
        * 如果该属性还未被设置，则会赋值 0 后再进行计算。
        * 如果传入负值，等同于减法操作。
        * ```
        * userAdd("total_revenue", 30); //此时total_revenue为30
        * userAdd("total_revenue", 648); //此时total_revenue为678
        * ```
        * @param key 累积属性key
        * @param value 累积的偏移值
        */
    userAdd(key:string, value:number):void {
        if(!this._userAddProperties){
            this._userAddProperties = {};
        }
        if(typeof value === "boolean"){
            value = value ? 1 : 0;
        }
        let total:number = Number(this._userAddProperties[key] || app.storage.getUValue(`analytics_user_add_${key}`, 0) ||0);
        this._userAddProperties[key] = total + value;
        app.storage.setUValue(`analytics_user_add_${key}`, total + value);
    }

    /**
     * 移除用户属性。当您要清空用户的用户属性值时，您可以调用 userUnset 来对指定属性进行清空操作，如果该属性还未在集群中被创建，则 user_unset 不会创建该属性
     * @param key 
     */
    userUnset(key:string):void {
        this._userProperties && delete this._userProperties[key];
        this._userOnceProperties && delete this._userOnceProperties[key];
        this._userAddProperties && delete this._userAddProperties[key];
        app.storage.removeUValue(`analytics_user_once_${key}`);
        app.storage.removeUValue(`analytics_user_add_${key}`);
    }

    /**
     * 上报错误
     * @param error 错误信息
     */
    error(error:string):void {
        this.report("app_error", {error:error});
    }

    /**
     * 开始事件计时
     * 如果您需要记录某个事件的持续时长，可以调用 timeEvent 来开始计时。
     * 配置您想要计时的事件名称，当您上传该事件时，将会自动在您的事件属性中加入 _dt_ 这一属性来表示记录的时长，单位为秒。
     * 需要注意的是，同一个事件名只能有一个在计时的任务。
     * @param eventName 
     */
    time(eventName:string):void {
        this._timeEvents = this._timeEvents || new Map();
        if(this._debugMode && this._timeEvents.has(eventName)){
            console.warn(`已经存在${eventName}的计时,将被重置。`);
        }
        this._timeEvents.set(eventName, app.App.durationOfLaunch);
    }

    /**
     * 上报事件
     * @param eventName 事件名称
     * @param data 事件数据
     */
    async report(eventName:string, data?:AnalyticsData):Promise<void> {
        if (!this._sendAble(eventName)) {
            this._debugMode && console.log(`[Analytics] ${eventName}事件不上报`);
            return;
        }
        if(!data){
            this._debugMode && console.warn(`[Analytics] ${eventName}无上报事件数据`);
            data = {};
        }else{
            data = Object.assign({}, data);
        }
        this._setDefaultProperty();
        
        for (let i = 0; i < this._dynamicCalls.length; i++) {
            const call = this._dynamicCalls[i];
            this._copyToData(eventName, data, call());
        }
        this._copyToData(eventName, data, this._userProperties);
        this._copyToData(eventName, data, this._userAddProperties);
        this._copyToData(eventName, data, this._userOnceProperties);
        this._copyToData(eventName, data, this._staticProperties);
        if(this._timeEvents && this._timeEvents.has(eventName)) {
            let startTime:number = this._timeEvents.get(eventName);
            let duration = Math.floor((app.App.durationOfLaunch - startTime) / 1000);
            data[InternalProperty.DURATION] = duration;
            this._timeEvents.delete(eventName);
        }
        this._debugMode && console.log("[Analytics]", eventName, JSON.stringify(data));
        return this._sendReport(eventName, data);
    }
    

    private _sendAble(eventName:string):boolean {
        for (let i = 0; i < this._senderList.length; i++) {
            const sender = this._senderList[i];
            if(sender.sendAble(eventName)){
                return true;
            }
        }
        return false;
    }

    private _copyToData(eventName:string, data:AnalyticsData, target:AnalyticsData):void {
        for (const key in target) {
            if (Object.prototype.hasOwnProperty.call(target, key)) {
                const value = target[key];
                if(data[key] == undefined) {
                    data[key] = value;
                }else{
                    console.warn(`[Analytics] 事件${eventName}存在重复属性${key}=${data[key]}`);
                }
            }
        }
    }

    protected async _sendReport(eventName:string, data:any):Promise<void>{
        return Promise.all(this._senderList.map((sender)=>{
            return sender.send(eventName, data);
        })).then();
    }
}
